package cn.yeziji.forum.filter;

import cn.yeziji.forum.common.AuthUser;
import cn.yeziji.forum.common.enums.ForumCommonStatus;
import cn.yeziji.forum.common.ForumResult;
import cn.yeziji.forum.common.key.UserCacheKey;
import cn.yeziji.forum.common.key.generate.UserCacheKeyGenerate;
import cn.yeziji.forum.exception.UserException;
import cn.yeziji.forum.common.enums.status.UserStatus;
import cn.yeziji.forum.utils.*;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

/**
 * jwt 登录过滤器
 *
 * @author gzkemays
 * @since 2021/12/21 16:50
 */
@Slf4j
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {
  private AuthenticationManager authenticationManager;
  /*  ForumUserDao userDao = SpringUtils.getBean(ForumUserDao.class);
  Cache<String, Object> filterLoginCache = SpringUtils.getBean("filterLoginCache");*/

  CacheUtils cache = SpringUtils.getBean(CacheUtils.class);

  public JwtLoginFilter(AuthenticationManager authenticationManager) {
    this.authenticationManager = authenticationManager;
    // 放行接口
    super.setFilterProcessesUrl("/forum/login");
  }

  /**
   * 解析用户凭证
   *
   * <p>过滤器筛选如果账号是未注册过的用户就将数据加入本地 caffeine 缓存。
   *
   * @param request 请求
   * @param response 响应
   * @return 用户凭证
   * @throws AuthenticationException 解析异常
   */
  @Override
  public Authentication attemptAuthentication(
      HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    try {
      AuthUser user = new ObjectMapper().readValue(request.getInputStream(), AuthUser.class);
      /*      ForumUserEntity userEntity = userDao.selectUserByUserName(user.getUsername());
      if (userEntity == null) {
        UserRegisterBO userRegisterBO = new UserRegisterBO(user);
        filterLoginCache.put(user.getUsername(), userRegisterBO);
      }*/
      return authenticationManager.authenticate(
          new UsernamePasswordAuthenticationToken(
              user.getUsername(), user.getPassword(), new ArrayList<>()));
    } catch (IOException e) {
      e.printStackTrace();
      throw new RuntimeException(e);
    }
  }

  @Override
  protected void successfulAuthentication(
      HttpServletRequest request,
      HttpServletResponse response,
      FilterChain chain,
      Authentication authResult) {
    AuthUser user = (AuthUser) authResult.getPrincipal();
    UserCacheKeyGenerate generate = UserCacheKey.GENERATE;
    String token;
    JSONObject json;
    if ((token = cache.get(generate.getSecondaryTokenKey(user.getUsername()))) == null) {
      json =
          JsonUtils.parseJsonByPojoExParent(
              user,
              "password",
              "updateTime",
              "createTime",
              "delete",
              "credentialsNonExpired",
              "accountNonExpired",
              "accountNonLocked",
              "enabled");
      token = generateJwtToken(generate, json);
    } else {
      json = cache.get(generate.getTokenCacheKey(token));
      if (json == null) {
        // FIXME: 不知道什么情况下 getTokenCacheKey 被消除，但是 SecondsToken 没被消除
        json =
            JsonUtils.parseJsonByPojoExParent(
                user,
                "password",
                "updateTime",
                "createTime",
                "delete",
                "credentialsNonExpired",
                "accountNonExpired",
                "accountNonLocked",
                "enabled");
        cache.set(generate.getTokenCacheKey(token), json, 30, TimeUnit.DAYS);
      }
      // 如果 token 已经过期，则生成新的 token
      if (JwtOperaUtils.isExpired(token)) {
        cache.remove(generate.getTokenCacheKey(token));
        token = generateJwtToken(generate, json);
      }
    }
    response.addHeader(HttpHeaders.AUTHORIZATION, token);
    // 设置 HttpServletResponse使用utf-8编码
    response.setCharacterEncoding("utf-8");
    // 设置响应头的编码
    response.setHeader("Content-Type", "application/json; charset=utf-8");
    ForumResult forumResult = ForumResultUtils.forumStatus(ForumCommonStatus.LOGIN_SUCCESS);
    // 删除 token 必要但返回数据不必要的信息
    json.remove("id");
    json.remove("exp");
    json.remove("iat");
    forumResult.setData(json);
    ResponseUtils.writeJson(response, forumResult);
    //        super.successfulAuthentication(request, response, chain, authResult);
  }

  @Override
  protected void unsuccessfulAuthentication(
      HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
    log.error("登录失败 --> {}", failed.getMessage());
    // 设置 HttpServletResponse使用utf-8编码
    response.setCharacterEncoding("utf-8");
    if (failed instanceof InternalAuthenticationServiceException) {
      ResponseUtils.writeJson(
          response, ForumResultUtils.exception(new UserException(UserStatus.USER_IS_NOT_FOUND)));
      return;
    }

    if (failed instanceof BadCredentialsException) {
      ResponseUtils.writeJson(
          response, ForumResultUtils.exception(new UserException(UserStatus.PASSWORD_ERROR)));
      return;
    }

    // 设置响应头的编码
    response.setHeader("Content-Type", "application/json; charset=utf-8");
    ResponseUtils.writeJson(response, ForumResultUtils.forumStatus(ForumCommonStatus.LOGIN_FAIL));
    //        super.unsuccessfulAuthentication(request, response, failed);
  }

  private String generateJwtToken(UserCacheKeyGenerate generate, JSONObject json) {
    String token = JwtOperaUtils.generateJsonWebTokenByJson(json);
    // 将用户的 token 存储入 redis 中方便续期
    cache.set(generate.getTokenCacheKey(token), json, 30, TimeUnit.DAYS);
    // 登记用户登录的次要缓存
    cache.set(generate.getSecondaryTokenKey(json.getString("username")), token, 30, TimeUnit.DAYS);
    return token;
  }
}
